
#define _GNU_SOURCE     // for asprintf

#define READ_END 0
#define WRITE_END 1

#include <signal.h>
#include <string.h>
#include "hw2_common.h"

void outputMsg (pid_t pid, char *role, char *msg);
void child_handler_sigusr1 (int sig_num);
void handler_sigint (int sig_num);
void handler_sigchld (int sig_num);

typedef struct _pipe_data {
    pid_t pid_1, pid_2, pid_3;
} pipe_data_t;

volatile sig_atomic_t _child_count = 0;

sigset_t sigchld_mask_new, sigchld_mask_old;

int main(void)
{    
    pid_t procid[3];

    int i;

    // [0] = read end
    // [1] = write end
    int pipefd[3][2];

    // child's SIGINT handler
    signal (SIGINT, handler_sigint);

    signal (SIGCHLD, handler_sigchld);

    // create 3 child
    //
    for (i=0; i < 3; i++) {
        
        // create pipe
        my_pipe ( pipefd[i] );

        procid[i] = my_fork ();
    
        if (procid[i] != 0) {        /* parent process, writer */
        
            // close unused end (read end) of pipe           
            my_close ( pipefd[i][READ_END] );

            // Display ID of child processes
            //
            char *msg;
            asprintf( &msg, "process ID of child%d = %d", i+1, procid[i] );
            outputMsg (getpid(), "parent", msg);
            free (msg);
            
            // Write processes ID to pipe
            //
            if (i == 2) {

                int j;
                for (j=0; j<=i; j++) {

                    // copy 3 child pid's into buffer 
                    //
                    pipe_data_t sent_data;
                    sent_data.pid_1 = procid[0];
                    sent_data.pid_2 = procid[1];
                    sent_data.pid_3 = procid[2];

                    pipe_data_t *buff = (pipe_data_t*) malloc (sizeof(pipe_data_t));
                    memcpy (buff, &sent_data, sizeof(pipe_data_t));

//                    pipe_data_t *buff_1 = (pipe_data_t*) memcpy (buff_1, &data, sizeof(pipe_data_t));

                    //pid_t *buff_1 = (pid_t*) memcpy (buff_1, &procid[0], sizeof(pid_t));
                    //pid_t *buff_2 = buff_1 + sizeof(pid_t);   
                    //pid_t *buff_3 = buff_2 + sizeof(pid_t);

//                    pid_t *buff_2 = (pid_t*) memcpy (buff_2, &procid[0], sizeof(pid_t));
//                    pid_t *buff_3 = (pid_t*) memcpy (buff_3, &procid[0], sizeof(pid_t));

                    // parent writes these pid's to pipe of each child process
                    //
                    //my_write ( pipefd[j][WRITE_END], buff_1, sizeof(pid_t) );
                    //my_write ( pipefd[j][WRITE_END], buff_1, sizeof(pid_t) );
                    //my_write ( pipefd[j][WRITE_END], buff_1, sizeof(pid_t) );

                    //my_write ( pipefd[j][1], buff_1, 3*sizeof(pid_t) );
                                    
//                    free (buff_1);
//                    free (buff_2);
//                    free (buff_3);

                    // parent writes these pid's to pipe of each child process
                    my_write ( pipefd[j][WRITE_END], buff, sizeof(pipe_data_t) );

                    my_close ( pipefd[j][WRITE_END] );

                    free (buff);

                    // signal child that all data is now available in pipe
                    my_kill (procid[j], SIGUSR1);
                }

                // parent waits for each child to terminate
                //                
                sigemptyset (&sigchld_mask_new);
                sigaddset (&sigchld_mask_new, SIGCHLD);

                /* Block SIGCHLD signal while checking for condition */
                sigprocmask (SIG_SETMASK, &sigchld_mask_new, &sigchld_mask_old);

                while (_child_count < 3) {

                    // un-block SIGCHLD signal
                    sigprocmask (SIG_SETMASK, &sigchld_mask_old, NULL);

                    // suspend parent until SIGCHLD arrives
                    sigset_t term_mask;
                    sigfillset (&term_mask);
                    sigdelset (&term_mask, SIGCHLD);
                    sigsuspend (&term_mask);                    
                }

                outputMsg (getpid(), "parent", "parent exits");
                exit (0);
            }                      
        }
        else {                   /* child process, reader */
            // close write end of pipe
            my_close ( pipefd[i][WRITE_END] );

            // block SIGINT signal for now
            //
            sigset_t sigint_mask_new, sigint_mask_old;
            sigemptyset (&sigint_mask_new);
            sigaddset (&sigint_mask_new, SIGINT);
            sigprocmask (SIG_SETMASK, &sigint_mask_new, &sigint_mask_old);


            signal (SIGUSR1, child_handler_sigusr1);

            // Suspend this child process until a SIGUSR1 from parent arrives
            //
            sigset_t block_mask;            
            sigfillset (&block_mask);
            sigdelset (&block_mask, SIGUSR1);
            
            sigsuspend (&block_mask);
            
            // now child should read all pid's from pipe
            //
            pid_t child_id[3];

            pipe_data_t *received_data = (pipe_data_t*) malloc (sizeof(pipe_data_t));

            my_read ( pipefd[i][READ_END], received_data, sizeof(pipe_data_t) );

            child_id[0] = received_data->pid_1;
            child_id[1] = received_data->pid_2;
            child_id[2] = received_data->pid_3;

            free (received_data);
            
            pid_t max = 0;
            char found = 0;

            int k;
            for (k=0; k < 3; k++) {

                //pid_t *temp = (pid_t *) malloc (sizeof(pid_t));

                ////&child_id[k] = (pid_t*) malloc (sizeof(pid_t));
                //my_read ( pipefd[i][READ_END], temp, sizeof(pid_t) );
                ////my_read ( pipefd[i][READ_END], &child_id[k], sizeof(pid_t) );

                //child_id[k] = *temp;

                // find highest PID
                //
                if ( getpid() == child_id[k] ) {
                    found = 1;
                }
                if (child_id[k] > max) {
                    max = child_id[k];
                }

                //free (temp);
            }

            my_close ( pipefd[i][READ_END] );

            char *msg;                
            asprintf( &msg, "child%d", i+1 );

            if ( getpid() == max ) {                                  
                outputMsg (getpid(), msg, "PID is the highest");

                // send SIGINT to other 2 child processes and then exit
                for (k=0; k<3; k++) {
                    if (child_id[k] != getpid()) {

                        my_kill (child_id[k], SIGINT);
                    }
                }
            }
            else {
                outputMsg (getpid(), msg, "PID is NOT the highest");                                

                /* un-block SIGINT, ready to handle this signal */
                sigprocmask (SIG_SETMASK, &sigint_mask_new, &sigint_mask_old);

                // wait for SIGINT and then exit
                //
                sigset_t block_mask;
                sigfillset (&block_mask);
                sigdelset (&block_mask, SIGINT);
                sigsuspend (&block_mask);

                outputMsg (getpid(), msg, "received SIGINT");                
            }

            free (msg);
            _exit (0);
            //break;
        }
    }

    exit (0);
    //return 0;
}


void outputMsg (pid_t pid, char *role, char *msg) {

    printf ( "(PID= %d, ID= %s) : %s \n", pid, role, msg);
}


void child_handler_sigusr1 (int sig_num) {
    // do nothing
    return;
}


void handler_sigint (int sig_num) {
    // do nothing
    return;
}


void handler_sigchld (int sig_num) {

    sigemptyset (&sigchld_mask_new);
    sigaddset (&sigchld_mask_new, SIGCHLD);

    /* Block SIGCHLD signal while doing computation */
    sigprocmask (SIG_SETMASK, &sigchld_mask_new, &sigchld_mask_old);

    _child_count = _child_count + 1;
    //printf("count = %d \n", _child_count);
}
